/*
 *  Copyright (c) 2017, The OpenThread Authors.
 *  All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions are met:
 *  1. Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *  2. Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 *  3. Neither the name of the copyright holder nor the
 *     names of its contributors may be used to endorse or promote products
 *     derived from this software without specific prior written permission.
 *
 *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 *  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 *  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 *  ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 *  LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 *  CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 *  SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 *  INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 *  CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 *  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 *  POSSIBILITY OF SUCH DAMAGE.
 */

#include <ctype.h>

#include <openthread/openthread.h>

#include "common/code_utils.hpp"
#include "common/instance.hpp"
#include "ncp/spinel_decoder.hpp"

#include "test_util.h"

namespace ot {
namespace Ncp {

enum
{
    kTestBufferSize = 800,
};


// Dump the buffer content to screen.
void DumpBuffer(const char *aTextMessage, uint8_t *aBuffer, uint16_t aBufferLength)
{
    enum
    {
        kBytesPerLine = 32,    // Number of bytes per line.
    };

    char charBuff[kBytesPerLine + 1];
    uint16_t counter;
    uint8_t byte;

    printf("\n%s - len = %u\n    ", aTextMessage, aBufferLength);

    counter = 0;

    while (aBufferLength--)
    {
        byte = *aBuffer++;
        printf("%02X ", byte);
        charBuff[counter] = isprint(byte) ? static_cast<char>(byte) : '.';
        counter++;

        if (counter == kBytesPerLine)
        {
            charBuff[counter] = 0;
            printf("    %s\n    ", charBuff);
            counter = 0;
        }
    }

    charBuff[counter] = 0;

    while (counter++ < kBytesPerLine)
    {
        printf("   ");
    }

    printf("    %s\n", charBuff);
}

void TestSpinelDecoder(void)
{
    uint8_t buffer[kTestBufferSize];
    SpinelDecoder decoder;

    spinel_ssize_t frameLen;

    const bool           kBool_1    = true;
    const bool           kBool_2    = false;
    const uint8_t        kUint8     = 0x42;
    const int8_t         kInt8      = -73;
    const uint16_t       kUint16    = 0xabcd;
    const int16_t        kInt16     = -567;
    const uint32_t       kUint32    = 0xdeadbeef;
    const int32_t        kInt32     = -123455678L;
    const uint64_t       kUint64    = 0xfe10dc32ba549876ULL;
    const int64_t        kInt64     = -9197712039090021561LL;

    const unsigned int   kUint_1    = 9;
    const unsigned int   kUint_2    = 0xa3;
    const unsigned int   kUint_3    = 0x8765;
    const unsigned int   kUint_4    = SPINEL_MAX_UINT_PACKED - 1;


    const spinel_ipv6addr_t kIp6Addr =
    {
        { 0x6B, 0x41, 0x65, 0x73, 0x42, 0x68, 0x61, 0x76, 0x54, 0x61, 0x72, 0x7A, 0x49, 0x69, 0x61, 0x4E }
    };

    const spinel_eui48_t kEui48 =
    {
        { 4, 8, 15, 16, 23, 42 }                // "Lost" EUI48!
    };

    const spinel_eui64_t kEui64 =
    {
        { 2, 3, 5, 7, 11, 13, 17, 19 },         // "Prime" EUI64!
    };

    const char kString_1[] = "OpenThread";
    const char kString_2[] = "";

    const uint16_t kData[] = { 10, 20, 3, 15, 1000, 60, 16 }; // ... then comes 17,18,19,20  :)

    bool b_1, b_2;
    uint8_t u8;
    int8_t i8;
    uint16_t u16;
    int16_t i16;
    uint32_t u32;
    int32_t i32;
    uint64_t u64;
    int64_t i64;
    unsigned int u_1, u_2, u_3, u_4;
    const spinel_ipv6addr_t *ip6Addr;
    const spinel_eui48_t *eui48;
    const spinel_eui64_t *eui64;
    const char *utf_1;
    const char *utf_2;
    const uint8_t *dataPtr_1;
    const uint8_t *dataPtr_2;
    uint16_t dataLen_1;
    uint16_t dataLen_2;

    memset(buffer, 0, sizeof(buffer));

    printf("\n- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -");
    printf("\nTest 1: Decoding simple types");

    frameLen = spinel_datatype_pack(
                   buffer,
                   sizeof(buffer),
                   (
                       SPINEL_DATATYPE_BOOL_S
                       SPINEL_DATATYPE_BOOL_S
                       SPINEL_DATATYPE_UINT8_S
                       SPINEL_DATATYPE_INT8_S
                       SPINEL_DATATYPE_UINT16_S
                       SPINEL_DATATYPE_INT16_S
                       SPINEL_DATATYPE_UINT32_S
                       SPINEL_DATATYPE_INT32_S
                       SPINEL_DATATYPE_UINT64_S
                       SPINEL_DATATYPE_INT64_S
                       SPINEL_DATATYPE_UINT_PACKED_S
                       SPINEL_DATATYPE_UINT_PACKED_S
                       SPINEL_DATATYPE_UINT_PACKED_S
                       SPINEL_DATATYPE_UINT_PACKED_S
                       SPINEL_DATATYPE_IPv6ADDR_S
                       SPINEL_DATATYPE_EUI48_S
                       SPINEL_DATATYPE_EUI64_S
                       SPINEL_DATATYPE_UTF8_S
                       SPINEL_DATATYPE_UTF8_S
                       SPINEL_DATATYPE_DATA_WLEN_S
                       SPINEL_DATATYPE_DATA_S
                   ),
                   kBool_1,
                   kBool_2,
                   kUint8,
                   kInt8,
                   kUint16,
                   kInt16,
                   kUint32,
                   kInt32,
                   kUint64,
                   kInt64,
                   kUint_1,
                   kUint_2,
                   kUint_3,
                   kUint_4,
                   &kIp6Addr,
                   &kEui48,
                   &kEui64,
                   kString_1,
                   kString_2,
                   kData, sizeof(kData),
                   kData, sizeof(kData)
               );

    DumpBuffer("Packed Spinel Frame", buffer, static_cast<uint16_t>(frameLen));

    decoder.Init(buffer, static_cast<uint16_t>(frameLen));

    VerifyOrQuit(decoder.GetFrame() == &buffer[0],                                "GetFrame() failed.");
    VerifyOrQuit(decoder.GetLength() == frameLen,                                 "GetLength() failed.");

    VerifyOrQuit(decoder.GetReadLength() == 0,                                    "GetReadLength() failed.");
    VerifyOrQuit(decoder.GetRemainingLength() == frameLen,                        "GetRemainingLength() failed.");
    VerifyOrQuit(decoder.IsAllRead() == false,                                    "IsAllRead() failed.");

    SuccessOrQuit(decoder.ReadBool(b_1),                                          "ReadBool() failed.");
    SuccessOrQuit(decoder.ReadBool(b_2),                                          "ReadBool() failed.");
    SuccessOrQuit(decoder.ReadUint8(u8),                                          "ReadUint8() failed.");
    SuccessOrQuit(decoder.ReadInt8(i8),                                           "ReadUint8() failed.");
    SuccessOrQuit(decoder.ReadUint16(u16),                                        "ReadUint16() failed.");
    SuccessOrQuit(decoder.ReadInt16(i16),                                         "ReadInt16() failed.");
    SuccessOrQuit(decoder.ReadUint32(u32),                                        "ReadUint32() failed.");
    SuccessOrQuit(decoder.ReadInt32(i32),                                         "ReadUint32() failed.");
    SuccessOrQuit(decoder.ReadUint64(u64),                                        "ReadUint64() failed.");
    SuccessOrQuit(decoder.ReadInt64(i64),                                         "ReadUint64() failed.");

    // Check the state
    VerifyOrQuit(decoder.GetReadLength() != 0,                                    "GetReadLength() failed.");
    VerifyOrQuit(decoder.GetRemainingLength() == frameLen - decoder.GetReadLength(),
                 "GetRemainingLength() failed.");
    VerifyOrQuit(decoder.IsAllRead() == false,                                    "IsAllRead() failed.");

    SuccessOrQuit(decoder.ReadUintPacked(u_1),                                    "ReadUintPacked() failed.");

    SuccessOrQuit(decoder.ReadUintPacked(u_2),                                    "ReadUintPacked() failed.");
    SuccessOrQuit(decoder.ReadUintPacked(u_3),                                    "ReadUintPacked() failed.");
    SuccessOrQuit(decoder.ReadUintPacked(u_4),                                    "ReadUintPacked() failed.");
    SuccessOrQuit(decoder.ReadIp6Address(ip6Addr),                                "ReadIp6Addr() failed.");
    SuccessOrQuit(decoder.ReadEui48(eui48),                                       "ReadEui48() failed.");
    SuccessOrQuit(decoder.ReadEui64(eui64),                                       "ReadEui64() failed.");
    SuccessOrQuit(decoder.ReadUtf8(utf_1),                                        "ReadUtf8() failed.");
    SuccessOrQuit(decoder.ReadUtf8(utf_2),                                        "ReadUtf8() failed.");
    SuccessOrQuit(decoder.ReadDataWithLen(dataPtr_1, dataLen_1),                  "ReadDataWithLen() failed.");
    SuccessOrQuit(decoder.ReadData(dataPtr_2, dataLen_2),                         "ReadData() failed.");

    VerifyOrQuit(decoder.GetReadLength() == frameLen,                            "GetReadLength() failed.");
    VerifyOrQuit(decoder.GetRemainingLength() == 0,                              "GetRemainingLength() failed.");
    VerifyOrQuit(decoder.IsAllRead() == true,                                    "IsAllRead() failed.");

    VerifyOrQuit(b_1 == kBool_1,                                                  "ReadBool() parse failed.");
    VerifyOrQuit(b_2 == kBool_2,                                                  "ReadBool() parse failed.");
    VerifyOrQuit(u8  == kUint8,                                                   "ReadUint8() parse failed.");
    VerifyOrQuit(i8  == kInt8,                                                    "ReadUint8() parse failed.");
    VerifyOrQuit(u16 == kUint16,                                                  "ReadUint16() parse failed.");
    VerifyOrQuit(i16 == kInt16,                                                   "ReadInt16() parse failed.");
    VerifyOrQuit(u32 == kUint32,                                                  "ReadUint32() parse failed.");
    VerifyOrQuit(i32 == kInt32,                                                   "ReadInt32() parse failed.");
    VerifyOrQuit(u64 == kUint64,                                                  "ReadUint64() parse failed.");
    VerifyOrQuit(i64 == kInt64,                                                   "ReadInt64() parse failed.");
    VerifyOrQuit(u_1 == kUint_1,                                                  "ReadUintPacked() parse failed.");
    VerifyOrQuit(u_2 == kUint_2,                                                  "ReadUintPacked() parse failed.");
    VerifyOrQuit(u_3 == kUint_3,                                                  "ReadUintPacked() parse failed.");
    VerifyOrQuit(u_4 == kUint_4,                                                  "ReadUintPacked() parse failed.");
    VerifyOrQuit(memcmp(ip6Addr, &kIp6Addr, sizeof(spinel_ipv6addr_t)) == 0,      "ReadIp6Address() parse failed.");
    VerifyOrQuit(memcmp(eui48, &kEui48, sizeof(spinel_eui48_t)) == 0,             "ReadEui48() parse failed.");
    VerifyOrQuit(memcmp(eui64, &kEui64, sizeof(spinel_eui64_t)) == 0,             "ReadEui64() parse failed.");
    VerifyOrQuit(memcmp(utf_1, kString_1, sizeof(kString_1)) == 0,                "ReadUtf8() parse failed.");
    VerifyOrQuit(memcmp(utf_2, kString_2, sizeof(kString_2)) == 0,                "ReadUtf8() parse failed.");
    VerifyOrQuit(dataLen_1 == sizeof(kData),                                      "ReadData() parse failed.");
    VerifyOrQuit(memcmp(dataPtr_1, &kData, sizeof(kData)) == 0,                   "ReadData() parse failed.");
    VerifyOrQuit(dataLen_2 == sizeof(kData),                                      "ReadData() parse failed.");
    VerifyOrQuit(memcmp(dataPtr_2, &kData, sizeof(kData)) == 0,                   "ReadData() parse failed.");

    printf(" -- PASS\n");

    printf("\n- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -");
    printf("\nTest 2: Test Reset(), SavePosition(), ResetToSaved()");

    // `ResetToSaved()` should fail if there is no saved position
    VerifyOrQuit(decoder.ResetToSaved() == OT_ERROR_INVALID_STATE,                "ResetToSaved() did not fail");

    decoder.Reset();

    VerifyOrQuit(decoder.GetFrame() == &buffer[0],                                "GetFrame() failed.");
    VerifyOrQuit(decoder.GetLength() == frameLen,                                 "GetLength() failed.");
    VerifyOrQuit(decoder.GetReadLength() == 0,                                    "GetReadLength() failed.");
    VerifyOrQuit(decoder.GetRemainingLength() == frameLen,                        "GetRemainingLength() failed.");
    VerifyOrQuit(decoder.IsAllRead() == false,                                    "IsAllRead() failed.");

    SuccessOrQuit(decoder.ReadBool(b_1),                                          "ReadBool() failed.");
    SuccessOrQuit(decoder.ReadBool(b_2),                                          "ReadBool() failed.");
    SuccessOrQuit(decoder.ReadUint8(u8),                                          "ReadUint8() failed.");
    SuccessOrQuit(decoder.ReadInt8(i8),                                           "ReadUint8() failed.");
    SuccessOrQuit(decoder.ReadUint16(u16),                                        "ReadUint16() failed.");
    SuccessOrQuit(decoder.ReadInt16(i16),                                         "ReadInt16() failed.");
    SuccessOrQuit(decoder.ReadUint32(u32),                                        "ReadUint32() failed.");
    SuccessOrQuit(decoder.ReadInt32(i32),                                         "ReadUint32() failed.");

    // `ResetToSaved()` should fail if there is no saved position
    VerifyOrQuit(decoder.ResetToSaved() == OT_ERROR_INVALID_STATE,                "ResetToSaved() did not fail");

    // Save position
    decoder.SavePosition();

    SuccessOrQuit(decoder.ReadUint64(u64),                                        "ReadUint64() failed.");
    SuccessOrQuit(decoder.ReadInt64(i64),                                         "ReadUint64() failed.");
    SuccessOrQuit(decoder.ReadUintPacked(u_1),                                    "ReadUintPacked() failed.");
    SuccessOrQuit(decoder.ReadUintPacked(u_2),                                    "ReadUintPacked() failed.");
    SuccessOrQuit(decoder.ReadUintPacked(u_3),                                    "ReadUintPacked() failed.");
    SuccessOrQuit(decoder.ReadUintPacked(u_4),                                    "ReadUintPacked() failed.");
    SuccessOrQuit(decoder.ReadIp6Address(ip6Addr),                                "ReadIp6Addr() failed.");

    VerifyOrQuit(b_1 == kBool_1,                                                  "ReadBool() parse failed.");
    VerifyOrQuit(b_2 == kBool_2,                                                  "ReadBool() parse failed.");
    VerifyOrQuit(u8  == kUint8,                                                   "ReadUint8() parse failed.");
    VerifyOrQuit(i8  == kInt8,                                                    "ReadUint8() parse failed.");
    VerifyOrQuit(u16 == kUint16,                                                  "ReadUint16() parse failed.");
    VerifyOrQuit(i16 == kInt16,                                                   "ReadInt16() parse failed.");
    VerifyOrQuit(u32 == kUint32,                                                  "ReadUint32() parse failed.");
    VerifyOrQuit(i32 == kInt32,                                                   "ReadUint32() parse failed.");
    VerifyOrQuit(u64 == kUint64,                                                  "ReadUint64() parse failed.");
    VerifyOrQuit(i64 == kInt64,                                                   "ReadInt64() parse failed.");
    VerifyOrQuit(u_1 == kUint_1,                                                  "ReadUintPacked() parse failed.");
    VerifyOrQuit(u_2 == kUint_2,                                                  "ReadUintPacked() parse failed.");
    VerifyOrQuit(u_3 == kUint_3,                                                  "ReadUintPacked() parse failed.");
    VerifyOrQuit(u_4 == kUint_4,                                                  "ReadUintPacked() parse failed.");
    VerifyOrQuit(memcmp(ip6Addr, &kIp6Addr, sizeof(spinel_ipv6addr_t)) == 0,      "ReadIp6Address() parse failed.");

    SuccessOrQuit(decoder.ResetToSaved(),                                         "ResetToSaved() failed");

    SuccessOrQuit(decoder.ReadUint64(u64),                                        "ReadUint64() failed.");
    SuccessOrQuit(decoder.ReadInt64(i64),                                         "ReadUint64() failed.");
    SuccessOrQuit(decoder.ReadUintPacked(u_1),                                    "ReadUintPacked() failed.");
    SuccessOrQuit(decoder.ReadUintPacked(u_2),                                    "ReadUintPacked() failed.");
    SuccessOrQuit(decoder.ReadUintPacked(u_3),                                    "ReadUintPacked() failed.");
    SuccessOrQuit(decoder.ReadUintPacked(u_4),                                    "ReadUintPacked() failed.");
    SuccessOrQuit(decoder.ReadIp6Address(ip6Addr),                                "ReadIp6Addr() failed.");

    VerifyOrQuit(u64 == kUint64,                                                  "ReadUint64() parse failed.");
    VerifyOrQuit(i64 == kInt64,                                                   "ReadInt64() parse failed.");
    VerifyOrQuit(u_1 == kUint_1,                                                  "ReadUintPacked() parse failed.");
    VerifyOrQuit(u_2 == kUint_2,                                                  "ReadUintPacked() parse failed.");
    VerifyOrQuit(u_3 == kUint_3,                                                  "ReadUintPacked() parse failed.");
    VerifyOrQuit(u_4 == kUint_4,                                                  "ReadUintPacked() parse failed.");
    VerifyOrQuit(memcmp(ip6Addr, &kIp6Addr, sizeof(spinel_ipv6addr_t)) == 0,      "ReadIp6Address() parse failed.");

    // Go back to save position again.
    SuccessOrQuit(decoder.ResetToSaved(),                                         "ResetToSaved() failed");

    SuccessOrQuit(decoder.ReadUint64(u64),                                        "ReadUint64() failed.");
    SuccessOrQuit(decoder.ReadInt64(i64),                                         "ReadUint64() failed.");
    SuccessOrQuit(decoder.ReadUintPacked(u_1),                                    "ReadUintPacked() failed.");
    SuccessOrQuit(decoder.ReadUintPacked(u_2),                                    "ReadUintPacked() failed.");
    SuccessOrQuit(decoder.ReadUintPacked(u_3),                                    "ReadUintPacked() failed.");
    SuccessOrQuit(decoder.ReadUintPacked(u_4),                                    "ReadUintPacked() failed.");
    SuccessOrQuit(decoder.ReadIp6Address(ip6Addr),                                "ReadIp6Addr() failed.");

    VerifyOrQuit(u64 == kUint64,                                                  "ReadUint64() parse failed.");
    VerifyOrQuit(i64 == kInt64,                                                   "ReadInt64() parse failed.");
    VerifyOrQuit(u_1 == kUint_1,                                                  "ReadUintPacked() parse failed.");
    VerifyOrQuit(u_2 == kUint_2,                                                  "ReadUintPacked() parse failed.");
    VerifyOrQuit(u_3 == kUint_3,                                                  "ReadUintPacked() parse failed.");
    VerifyOrQuit(u_4 == kUint_4,                                                  "ReadUintPacked() parse failed.");
    VerifyOrQuit(memcmp(ip6Addr, &kIp6Addr, sizeof(spinel_ipv6addr_t)) == 0,      "ReadIp6Address() parse failed.");

    // Ensure saved position is cleared when decoder is reset or re-initialized.

    decoder.Reset();

    // `ResetToSaved()` should fail if there is no saved position
    VerifyOrQuit(decoder.ResetToSaved() == OT_ERROR_INVALID_STATE,                "ResetToSaved() did not fail");

    decoder.SavePosition();
    SuccessOrQuit(decoder.ResetToSaved(),                                         "ResetToSaved() failed");

    decoder.Init(buffer, static_cast<uint16_t>(frameLen));
    VerifyOrQuit(decoder.ResetToSaved() == OT_ERROR_INVALID_STATE,                "ResetToSaved() did not fail");

    printf(" -- PASS\n");

    printf("\n- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -");
    printf("\nTest 3: Test decoding a single simple struct.");

    frameLen = spinel_datatype_pack(
                   buffer,
                   sizeof(buffer),
                   (
                       SPINEL_DATATYPE_UINT8_S
                       SPINEL_DATATYPE_STRUCT_S(
                           SPINEL_DATATYPE_UINT32_S
                           SPINEL_DATATYPE_EUI48_S
                           SPINEL_DATATYPE_UINT_PACKED_S
                       )
                       SPINEL_DATATYPE_INT16_S

                   ),
                   kUint8,
                   kUint32,
                   &kEui48,
                   kUint_3,
                   kInt16
               );

    DumpBuffer("Packed Spinel Frame (single struct)", buffer, static_cast<uint16_t>(frameLen));

    decoder.Init(buffer, static_cast<uint16_t>(frameLen));

    SuccessOrQuit(decoder.ReadUint8(u8),                                          "ReadUint8() failed.");
    SuccessOrQuit(decoder.OpenStruct(),                                           "OpenStruct() failed.");
    {
        SuccessOrQuit(decoder.ReadUint32(u32),                                    "ReadUint32() failed.");
        SuccessOrQuit(decoder.ReadEui48(eui48),                                   "ReadEui48() failed.");
        SuccessOrQuit(decoder.ReadUintPacked(u_3),                                "ReadUintPacked() failed.");
    }
    SuccessOrQuit(decoder.CloseStruct(),                                          "CloseStruct() failed.");
    SuccessOrQuit(decoder.ReadInt16(i16),                                         "ReadInt16() failed.");
    VerifyOrQuit(decoder.IsAllRead() == true,                                     "IsAllRead() failed.");

    VerifyOrQuit(u8  == kUint8,                                                   "ReadUint8() parse failed.");
    VerifyOrQuit(i16 == kInt16,                                                   "ReadInt16() parse failed.");
    VerifyOrQuit(u32 == kUint32,                                                  "ReadUint32() parse failed.");
    VerifyOrQuit(u_3 == kUint_3,                                                  "ReadUintPacked() parse failed.");
    VerifyOrQuit(memcmp(eui48, &kEui48, sizeof(spinel_eui48_t)) == 0,             "ReadEui48() parse failed.");

    printf(" -- PASS\n");

    printf("\n- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -");
    printf("\nTest 4: Test partial struct read");

    // Re-use same frame as the previous test.

    decoder.Init(buffer, static_cast<uint16_t>(frameLen));

    SuccessOrQuit(decoder.ReadUint8(u8),                                          "ReadUint8() failed.");
    SuccessOrQuit(decoder.OpenStruct(),                                           "OpenStruct() failed.");
    {
        SuccessOrQuit(decoder.ReadUint32(u32),                                    "ReadUint32() failed.");
        // Skip the remaining fields in the struct
    }
    SuccessOrQuit(decoder.CloseStruct(),                                          "CloseStruct() failed.");
    SuccessOrQuit(decoder.ReadInt16(i16),                                         "ReadInt16() failed.");

    VerifyOrQuit(u8  == kUint8,                                                   "ReadUint8() parse failed.");
    VerifyOrQuit(i16 == kInt16,                                                   "ReadInt16() parse failed.");

    printf(" -- PASS\n");


    printf("\n- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -");
    printf("\nTest 5: Test `GetRemainingLengthInStruct()` and `IsAllReadInStruct`() in and out of an struct");

    // Re-use same frame as the previous test.

    decoder.Init(buffer, static_cast<uint16_t>(frameLen));

    VerifyOrQuit(decoder.GetFrame() == &buffer[0],                                "GetFrame() failed.");
    VerifyOrQuit(decoder.GetLength() == frameLen,                                 "GetLength() failed.");

    VerifyOrQuit(decoder.GetReadLength() == 0,                                    "GetReadLength() failed.");
    VerifyOrQuit(decoder.GetRemainingLength() == frameLen,                        "GetRemainingLength() failed.");
    VerifyOrQuit(decoder.IsAllRead() == false,                                    "IsAllRead() failed.");

    // When not in an struct,  `etRemainingLengthInStruct()` should consider the whole frame.
    VerifyOrQuit(decoder.GetRemainingLengthInStruct() == frameLen,                "GetRemLengthInStruct() failed.");
    VerifyOrQuit(decoder.IsAllReadInStruct() == false,                            "IsAllReadInStruct() failed.");

    SuccessOrQuit(decoder.ReadUint8(u8),                                          "ReadUint8() failed.");
    SuccessOrQuit(decoder.OpenStruct(),                                           "OpenStruct() failed.");
    {
        VerifyOrQuit(decoder.IsAllReadInStruct() == false,                        "IsAllReadInStruct() failed.");

        SuccessOrQuit(decoder.ReadUint32(u32),                                    "ReadUint32() failed.");
        SuccessOrQuit(decoder.ReadEui48(eui48),                                   "ReadEui48() failed.");
        SuccessOrQuit(decoder.ReadUintPacked(u_3),                                "ReadUintPacked() failed.");

        VerifyOrQuit(decoder.IsAllReadInStruct() == true,                         "IsAllReadInStruct() failed.");
        VerifyOrQuit(decoder.GetRemainingLengthInStruct() == 0,                   "GetRemLengthInStruct() failed.");

        // Try reading beyond end of the struct and ensure it fails.
        VerifyOrQuit(decoder.ReadUint8(u8) == OT_ERROR_PARSE,                     "ReadUint8() did not fail.");

        // `ReadData()` at end of struct should still succeed but return zero as the data length.
        SuccessOrQuit(decoder.ReadData(dataPtr_1, dataLen_1),                     "ReadData() failed.");
        VerifyOrQuit(dataLen_1 == 0,                                              "ReadData() parse value failed.");

    }
    SuccessOrQuit(decoder.CloseStruct(),                                          "CloseStruct() failed.");

    VerifyOrQuit(decoder.IsAllReadInStruct() == false,                            "IsAllReadInStruct() failed.");
    SuccessOrQuit(decoder.ReadInt16(i16),                                         "ReadInt16() failed.");
    VerifyOrQuit(decoder.IsAllRead() == true,                                     "IsAllRead() failed.");

    VerifyOrQuit(decoder.GetRemainingLengthInStruct() == 0,                       "GetRemLengthInStruct() failed.");
    VerifyOrQuit(decoder.IsAllReadInStruct() == true,                             "IsAllReadInStruct() failed.");

    // `ReadData()` at end of frame should still succeed but return zero as the data length.
    SuccessOrQuit(decoder.ReadData(dataPtr_1, dataLen_1),                         "ReadData() failed.");
    VerifyOrQuit(dataLen_1 == 0,                                                  "ReadData() parse value failed.");

    VerifyOrQuit(u8  == kUint8,                                                   "ReadUint8() parse failed.");
    VerifyOrQuit(i16 == kInt16,                                                   "ReadInt16() parse failed.");
    VerifyOrQuit(u32 == kUint32,                                                  "ReadUint32() parse failed.");
    VerifyOrQuit(u_3 == kUint_3,                                                  "ReadUintPacked() parse failed.");
    VerifyOrQuit(memcmp(eui48, &kEui48, sizeof(spinel_eui48_t)) == 0,             "ReadEui48() parse failed.");

    printf(" -- PASS\n");

    printf("\n- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -");
    printf("\nTest 6: Test multiple nested structs");

    frameLen = spinel_datatype_pack(
                   buffer,
                   sizeof(buffer),
                   (
                       SPINEL_DATATYPE_STRUCT_S(
                           SPINEL_DATATYPE_UINT8_S
                           SPINEL_DATATYPE_UTF8_S
                           SPINEL_DATATYPE_STRUCT_S(
                               SPINEL_DATATYPE_BOOL_S
                               SPINEL_DATATYPE_IPv6ADDR_S
                           )
                           SPINEL_DATATYPE_UINT16_S
                       )
                       SPINEL_DATATYPE_EUI48_S
                       SPINEL_DATATYPE_STRUCT_S(
                           SPINEL_DATATYPE_UINT32_S
                       )
                       SPINEL_DATATYPE_INT32_S
                   ),
                   kUint8,
                   kString_1,
                   kBool_1,
                   &kIp6Addr,
                   kUint16,
                   &kEui48,
                   kUint32,
                   kInt32
               );

    DumpBuffer("Packed Spinel Frame (multiple struct)", buffer, static_cast<uint16_t>(frameLen));

    decoder.Init(buffer, static_cast<uint16_t>(frameLen));

    SuccessOrQuit(decoder.OpenStruct(),                                           "OpenStruct() failed.");
    {
        SuccessOrQuit(decoder.ReadUint8(u8),                                      "ReadUint8() failed.");
        SuccessOrQuit(decoder.ReadUtf8(utf_1),                                    "ReadUtf8() failed.");
        SuccessOrQuit(decoder.OpenStruct(),                                       "OpenStruct() failed.");
        {
            SuccessOrQuit(decoder.ReadBool(b_1),                                  "ReadBool() failed.");
            SuccessOrQuit(decoder.ReadIp6Address(ip6Addr),                        "ReadIp6Addr() failed.");

        }
        SuccessOrQuit(decoder.CloseStruct(),                                      "CloseStruct() failed.");
        SuccessOrQuit(decoder.ReadUint16(u16),                                    "ReadUint16() failed.");
    }
    SuccessOrQuit(decoder.CloseStruct(),                                          "CloseStruct() failed.");
    SuccessOrQuit(decoder.ReadEui48(eui48),                                       "ReadEui48() failed.");
    SuccessOrQuit(decoder.OpenStruct(),                                           "OpenStruct() failed.");
    {
        SuccessOrQuit(decoder.ReadUint32(u32),                                    "ReadUint32() failed.");
    }
    SuccessOrQuit(decoder.CloseStruct(),                                          "CloseStruct() failed.");
    SuccessOrQuit(decoder.ReadInt32(i32),                                         "WriteUint32() failed.");

    VerifyOrQuit(decoder.GetReadLength() == frameLen,                             "GetReadLength() failed.");
    VerifyOrQuit(decoder.GetRemainingLength() == 0,                               "GetRemainingLength() failed.");
    VerifyOrQuit(decoder.IsAllRead() == true,                                     "IsAllRead() failed.");

    VerifyOrQuit(b_1 == kBool_1,                                                  "ReadBool() parse failed.");
    VerifyOrQuit(u8  == kUint8,                                                   "ReadUint8() parse failed.");
    VerifyOrQuit(u16 == kUint16,                                                  "ReadUint16() parse failed.");
    VerifyOrQuit(u32 == kUint32,                                                  "ReadUint32() parse failed.");
    VerifyOrQuit(i32 == kInt32,                                                   "ReadUint32() parse failed.");
    VerifyOrQuit(memcmp(ip6Addr, &kIp6Addr, sizeof(spinel_ipv6addr_t)) == 0,      "ReadIp6Address() parse failed.");
    VerifyOrQuit(memcmp(eui48, &kEui48, sizeof(spinel_eui48_t)) == 0,             "ReadEui48() parse failed.");
    VerifyOrQuit(memcmp(utf_1, kString_1, sizeof(kString_1)) == 0,                "ReadUtf8() parse failed.");

    printf(" -- PASS\n");

    printf("\n- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -");
    printf("\nTest 7: Test `SavePosition()`, `ResetToSaved()` for nested structs");

    // Re-use same frame as the previous test.

    decoder.Init(buffer, static_cast<uint16_t>(frameLen));

    SuccessOrQuit(decoder.OpenStruct(),                                           "OpenStruct() failed.");
    {
        SuccessOrQuit(decoder.ReadUint8(u8),                                      "ReadUint8() failed.");

        decoder.SavePosition();

        SuccessOrQuit(decoder.ReadUtf8(utf_1),                                    "ReadUtf8() failed.");
        SuccessOrQuit(decoder.OpenStruct(),                                       "OpenStruct() failed.");
        {
            SuccessOrQuit(decoder.ReadBool(b_1),                                  "ReadBool() failed.");
        }

        // Verify the read content so far.

        VerifyOrQuit(u8  == kUint8,                                               "ReadUint8() parse failed.");
        VerifyOrQuit(b_1 == kBool_1,                                                  "ReadBool() parse failed.");
        VerifyOrQuit(memcmp(utf_1, kString_1, sizeof(kString_1)) == 0,            "ReadUtf8() parse failed.");

        // Do not close the inner struct and jump to previously saved position and re-read the content.

        SuccessOrQuit(decoder.ResetToSaved(),                                     "ResetToSaved() failed.");

        SuccessOrQuit(decoder.ReadUtf8(utf_1),                                    "ReadUtf8() failed.");
        SuccessOrQuit(decoder.OpenStruct(),                                       "OpenStruct() failed.");
        {
            SuccessOrQuit(decoder.ReadBool(b_1),                                  "ReadBool() failed.");
            SuccessOrQuit(decoder.ReadIp6Address(ip6Addr),                        "ReadIp6Addr() failed.");

        }
        SuccessOrQuit(decoder.CloseStruct(),                                      "CloseStruct() failed.");
        SuccessOrQuit(decoder.ReadUint16(u16),                                    "ReadUint16() failed.");
    }
    SuccessOrQuit(decoder.CloseStruct(),                                          "CloseStruct() failed.");
    SuccessOrQuit(decoder.ReadEui48(eui48),                                       "ReadEui48() failed.");
    SuccessOrQuit(decoder.OpenStruct(),                                           "OpenStruct() failed.");
    {
        SuccessOrQuit(decoder.ReadUint32(u32),                                    "ReadUint32() failed.");
    }
    SuccessOrQuit(decoder.CloseStruct(),                                          "CloseStruct() failed.");
    SuccessOrQuit(decoder.ReadInt32(i32),                                         "WriteUint32() failed.");

    VerifyOrQuit(decoder.GetReadLength() == frameLen,                             "GetReadLength() failed.");
    VerifyOrQuit(decoder.GetRemainingLength() == 0,                               "GetRemainingLength() failed.");
    VerifyOrQuit(decoder.IsAllRead() == true,                                     "IsAllRead() failed.");

    VerifyOrQuit(b_1 == kBool_1,                                                  "ReadBool() parse failed.");
    VerifyOrQuit(u8  == kUint8,                                                   "ReadUint8() parse failed.");
    VerifyOrQuit(u16 == kUint16,                                                  "ReadUint16() parse failed.");
    VerifyOrQuit(u32 == kUint32,                                                  "ReadUint32() parse failed.");
    VerifyOrQuit(i32 == kInt32,                                                   "ReadUint32() parse failed.");
    VerifyOrQuit(memcmp(ip6Addr, &kIp6Addr, sizeof(spinel_ipv6addr_t)) == 0,      "ReadIp6Address() parse failed.");
    VerifyOrQuit(memcmp(eui48, &kEui48, sizeof(spinel_eui48_t)) == 0,             "ReadEui48() parse failed.");
    VerifyOrQuit(memcmp(utf_1, kString_1, sizeof(kString_1)) == 0,                "ReadUtf8() parse failed.");

    printf(" -- PASS\n");

    printf("\n- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -");
    printf("\nTest 8: Test saving position at start of an open struct");

    // Re-use same frame as the previous test.

    decoder.Init(buffer, static_cast<uint16_t>(frameLen));

    SuccessOrQuit(decoder.OpenStruct(),                                           "OpenStruct() failed.");
    {
        SuccessOrQuit(decoder.ReadUint8(u8),                                      "ReadUint8() failed.");
        SuccessOrQuit(decoder.ReadUtf8(utf_1),                                    "ReadUtf8() failed.");
        SuccessOrQuit(decoder.OpenStruct(),                                       "OpenStruct() failed.");
        {
            // Save position at start of the struct
            decoder.SavePosition();
            SuccessOrQuit(decoder.ReadBool(b_1),                                  "ReadBool() failed.");

            // Verify the read content so far.

            VerifyOrQuit(u8  == kUint8,                                           "ReadUint8() parse failed.");
            VerifyOrQuit(memcmp(utf_1, kString_1, sizeof(kString_1)) == 0,        "ReadUtf8() parse failed.");
            VerifyOrQuit(b_1 == kBool_1,                                          "ReadBool() parse failed.");

            // Do not close the struct and jump to the previously saved position and re-read the content.

            SuccessOrQuit(decoder.ResetToSaved(),                                     "ResetToSaved() failed.");
            SuccessOrQuit(decoder.ReadBool(b_1),                                  "ReadBool() failed.");
            SuccessOrQuit(decoder.ReadIp6Address(ip6Addr),                        "ReadIp6Addr() failed.");

        }
        SuccessOrQuit(decoder.CloseStruct(),                                      "CloseStruct() failed.");
        SuccessOrQuit(decoder.ReadUint16(u16),                                    "ReadUint16() failed.");
    }
    SuccessOrQuit(decoder.CloseStruct(),                                          "CloseStruct() failed.");
    SuccessOrQuit(decoder.ReadEui48(eui48),                                       "ReadEui48() failed.");
    SuccessOrQuit(decoder.OpenStruct(),                                           "OpenStruct() failed.");
    {
        SuccessOrQuit(decoder.ReadUint32(u32),                                    "ReadUint32() failed.");
    }
    SuccessOrQuit(decoder.CloseStruct(),                                          "CloseStruct() failed.");
    SuccessOrQuit(decoder.ReadInt32(i32),                                         "WriteUint32() failed.");

    VerifyOrQuit(decoder.GetReadLength() == frameLen,                             "GetReadLength() failed.");
    VerifyOrQuit(decoder.GetRemainingLength() == 0,                               "GetRemainingLength() failed.");
    VerifyOrQuit(decoder.IsAllRead() == true,                                     "IsAllRead() failed.");

    VerifyOrQuit(b_1 == kBool_1,                                                  "ReadBool() parse failed.");
    VerifyOrQuit(u8  == kUint8,                                                   "ReadUint8() parse failed.");
    VerifyOrQuit(u16 == kUint16,                                                  "ReadUint16() parse failed.");
    VerifyOrQuit(u32 == kUint32,                                                  "ReadUint32() parse failed.");
    VerifyOrQuit(i32 == kInt32,                                                   "ReadUint32() parse failed.");
    VerifyOrQuit(memcmp(ip6Addr, &kIp6Addr, sizeof(spinel_ipv6addr_t)) == 0,      "ReadIp6Address() parse failed.");
    VerifyOrQuit(memcmp(eui48, &kEui48, sizeof(spinel_eui48_t)) == 0,             "ReadEui48() parse failed.");
    VerifyOrQuit(memcmp(utf_1, kString_1, sizeof(kString_1)) == 0,                "ReadUtf8() parse failed.");

    printf(" -- PASS\n");

    printf("\n- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -");
    printf("\nTest 9: Test `ResetToSaved()` failure case (jumping back to a saved position closed struct).");

    // Re-use same frame as the previous test.

    decoder.Init(buffer, static_cast<uint16_t>(frameLen));

    SuccessOrQuit(decoder.OpenStruct(),                                           "OpenStruct() failed.");
    {
        SuccessOrQuit(decoder.ReadUint8(u8),                                      "ReadUint8() failed.");
        SuccessOrQuit(decoder.ReadUtf8(utf_1),                                    "ReadUtf8() failed.");
        SuccessOrQuit(decoder.OpenStruct(),                                       "OpenStruct() failed.");
        {
            SuccessOrQuit(decoder.ReadBool(b_1),                                  "ReadBool() failed.");

            decoder.SavePosition();

            SuccessOrQuit(decoder.ReadIp6Address(ip6Addr),                        "ReadIp6Addr() failed.");

        }
        SuccessOrQuit(decoder.CloseStruct(),                                      "CloseStruct() failed.");
        SuccessOrQuit(decoder.ReadUint16(u16),                                    "ReadUint16() failed.");

        // `ResetToSaved()` should fail sicne the enclosing struct for the saved position is closed.
        VerifyOrQuit(decoder.ResetToSaved() == OT_ERROR_INVALID_STATE,            "ResetToSaved() did not fail.");
    }

    printf(" -- PASS\n");

    printf("\n- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -");
    printf("\nTest 10: Testing error cases and failures. (e.g., wrong struct length).");

    frameLen = spinel_datatype_pack(
                   buffer,
                   sizeof(buffer),
                   (
                       SPINEL_DATATYPE_UINT8_S
                       SPINEL_DATATYPE_UINT16_S   // Treat this as struct length
                       SPINEL_DATATYPE_BOOL_S
                   ),
                   kUint8,
                   10,
                   kBool_1
               );

    DumpBuffer("Packed Spinel Frame (incorrect format)", buffer, static_cast<uint16_t>(frameLen));

    decoder.Init(buffer, static_cast<uint16_t>(frameLen));

    decoder.SavePosition();

    SuccessOrQuit(decoder.ReadUint8(u8),                                      "ReadUint8() failed.");
    VerifyOrQuit(u8  == kUint8,                                               "ReadUint8() parse failed.");

    // `OpenStruct()` should fail, since it expects a length 10 but there are not enough
    // bytes in the frame.
    VerifyOrQuit(decoder.OpenStruct() == OT_ERROR_PARSE,                      "OpenStruct() did not fail.");

    decoder.ResetToSaved();

    SuccessOrQuit(decoder.ReadUint8(u8),                                      "ReadUint8() failed.");
    VerifyOrQuit(u8  == kUint8,                                               "ReadUint8() parse failed.");
    VerifyOrQuit(decoder.ReadDataWithLen(dataPtr_1, dataLen_1) == OT_ERROR_PARSE,
                 "ReadDataWithLen() did not fail.");

    decoder.ResetToSaved();
    SuccessOrQuit(decoder.ReadUint8(u8),                                      "ReadUint8() failed.");
    SuccessOrQuit(decoder.ReadUint16(u16),                                    "ReadUint16() failed.");
    SuccessOrQuit(decoder.ReadBool(b_1),                                      "ReadUint16() failed.");

    // Try reading beyond end of frame.
    VerifyOrQuit(decoder.ReadUint8(u8) == OT_ERROR_PARSE,                     "ReadUint8() did not fail");

    printf(" -- PASS\n");
}

}  // namespace Ncp
}  // namespace ot

#ifdef ENABLE_TEST_MAIN
int main(void)
{
    ot::Ncp::TestSpinelDecoder();
    printf("\nAll tests passed.\n");
    return 0;
}
#endif
